namespace Aob.Lite.Dsl

import System

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.TypeSystem

class AspectMacro(AbstractAstMacro):
"""Description of AspectMacro"""
	
	override def Expand(macro as MacroStatement):
		AspectMacroHelper.ValidateArgs(macro)
		classDef = AspectMacroHelper.AddAspectClass(macro)
		ProcessPointcuts(classDef, macro.Block)

		pp = Parameters.Pipeline
		Parameters.Pipeline.AfterStep += def(sender, e as CompilerStepEventArgs):
			if e.Step isa Steps.ExpandMacros:
				# Re-run BindAndApplyAttributes since we've added some to the AST.
				pp.Get(typeof(Steps.BindAndApplyAttributes)).Run()
		
	private def ProcessPointcuts(classDef as ClassDefinition, existingBlock as Block):
		pointcutFinder = BlockFinder(Annotations.Pointcut)
		existingBlock.Accept(pointcutFinder)
		for p in pointcutFinder.Blocks:
			info as PointcutInfo = p[Annotations.Pointcut]
			ProcessPointcut(classDef, info)
		return pointcutFinder.Blocks

	private def ProcessPointcut(classDef as ClassDefinition, info as PointcutInfo):
		AspectMacroHelper.CreateAdviceMethod(_context, classDef, info)
		AspectMacroHelper.CreatePointcutExtension(_context, GetModule(info.Macro), info)
		
class AspectMacroHelper:

	private static final AdviceAttributeType = typeof(Aob.Lite.AdviceAttribute)	
	private static final ExtensionAttributeType = typeof(Boo.Lang.ExtensionAttribute)	
	
	static def ValidateArgs(macro as MacroStatement):
		assert 1 == macro.Arguments.Count
		assert macro.Arguments[0] isa ReferenceExpression
		assert GetParentMacro(macro) is null

	static def AddAspectClass(macro as MacroStatement):
		aspectName = FormatAspectName(macro.Arguments[0].ToString())
		classDef = ClassDefinition(Name: aspectName)
		
		module = GetModule(macro)
		module.Members.Add(classDef)
	
		return classDef

	static private def FormatAspectName(name as string):
		if name.EndsWith("Aspect") or name.EndsWith("aspect"):
			return name
		return "${name}Aspect"
		
	static def CreateAdviceMethod(context as CompilerContext, classDef as ClassDefinition, info as PointcutInfo):
		adviceBlock = info.AdviceBlocks[0]
		parms = CreateParametersCollectionFromPointcutInfo(context, info)
		typeRef = context.CodeBuilder.CreateTypeReference(Joinpoint)
		parms.Insert(0, ParameterDeclaration("invocation", typeRef))
		method = AddSingleStaticMethod(classDef, 
						FormatAdviceName(info.Macro.Arguments[0].ToString()),
						parms,
						adviceBlock)
		assert method is not null
		
		method.Attributes.Add(CreateAdviceAttribute(info.Macro.Arguments[0]))
		return method

	static def CreateParametersCollectionFromPointcutInfo(context as CompilerContext, info as PointcutInfo):
		adviceBlock = info.AdviceBlocks[0]
		adviceInfo = adviceBlock[Annotations.Advice] as AdviceInfo

		return ParametersFromMacroArguments(adviceInfo.Macro.Arguments)
		
	static private def CreateAdviceAttribute(pointcutExpression as Expression):
		attr = Ast.Attribute(AdviceAttributeType.FullName)
		attr.Arguments.Add(pointcutExpression)
		return attr
						
	static private def FormatAdviceName(name as string):
		return "${name.Replace(char('.'), char('_'))}Advice"

	static def CreatePointcutExtension(context as CompilerContext, module as Module, info as PointcutInfo):
		parms = CreateParametersCollectionFromPointcutInfo(context, info)
		typeRef = context.CodeBuilder.CreateTypeReference(object)
		parms.Insert(0, ParameterDeclaration("pointcutObject", typeRef))
		method = AddSingleStaticMethod(module,
						FormatPointcutExtensionName(info.Macro.Arguments[0].ToString()),
						parms,
						Ast.ReturnStatement())
		assert method is not null

		method.Attributes.Add(Ast.Attribute(ExtensionAttributeType.FullName))

		return method
	
	static private def FormatPointcutExtensionName(name as string):
		name = name.Replace(".", "_")
		if name.EndsWith("Pointcut") or name.EndsWith("pointcut"):
			return "${name}Extension"
		return "${name}PointcutExtension"

